/*
 * 书籍迁移 - 书籍数据迁移定义
 * 
 * 主要功能：
 * - 定义书籍数据的数据库迁移
 * - 创建书籍索引表结构
 * - 演示YesSql迁移的使用
 * - 提供数据库架构管理
 */

using Lombiq.TrainingDemo.Indexes;
using OrchardCore.Data.Migration;
using System.Threading.Tasks;
using YesSql.Sql;

namespace Lombiq.TrainingDemo.Migrations;

/// <summary>
/// 书籍迁移 - 定义书籍数据的数据库迁移
/// 
/// 主要用途：
/// - 定义书籍数据的数据库迁移
 //* - 创建书籍索引表结构
 //* - 演示YesSql迁移的使用
 //* - 提供数据库架构管理
 //* 
 //* 使用场景：
/// - 初始化书籍索引表
/// - 创建数据库索引
/// - 演示数据库架构变更
/// - 提供版本化的数据库管理
/// 
/// 设计原因：
/// - 为书籍数据提供数据库迁移支持
/// - 实现数据库架构的版本控制
/// - 支持数据库索引的创建
/// - 提供可重复的数据库设置
/// 
/// 设计模式：
/// - 迁移模式：继承DataMigration实现数据库迁移
/// - 版本模式：使用版本号管理迁移
/// - 架构模式：使用SchemaBuilder管理数据库架构
/// - 索引模式：创建数据库索引以提高查询性能
/// </summary>
// 之前我们已经看到了如何描述索引。我们还必须声明如何存储它。这就是我们需要迁移的地方。
// 迁移由框架自动运行（在您在服务提供程序中注册它之后）。您可以使用它们来描述数据库架构变更。
// 不要忘记在服务提供程序中注册此类（请参阅：Startup.cs）。
public sealed class BookMigrations : DataMigration
{
    /// <summary>
    /// 创建异步方法 - 创建初始数据库架构
    /// 
    /// 功能说明：
    /// - 创建书籍索引表结构
    /// - 定义表列和数据类型
    /// - 创建数据库索引
    /// - 设置初始版本号
    /// 
    /// 架构设计：
    /// - Author列：存储作者信息
    /// - Title列：存储书籍标题（无限制长度）
    /// - 索引：为Author列创建索引以提高查询性能
    /// 
    /// 性能考虑：
    /// - 为频繁查询的列添加索引
    /// - 使用无限制长度存储长标题
    /// - 优化数据库查询性能
    /// </summary>
    /// <returns>迁移版本号</returns>
    // 迁移有Create()和UpdateFromX方法。当模块首次启用时，调用Create()以便它可以设置数据库表。
    public async Task<int> CreateAsync()
    {
        await SchemaBuilder.CreateMapIndexTableAsync<BookIndex>(table => table
            .Column<string>(nameof(BookIndex.Author))
            // 书籍标题有时可能真的很长（甚至长达26000个字符：
            // https://www.guinnessworldrecords.com/world-records/358711-longest-title-of-a-book），
            // 所以我们必须确保它能适合列。默认情况下，文本列有255个字符的限制，所以我们必须强制执行，
            // 并在确实需要空间的情况下使其无限制。
            .Column<string>(nameof(BookIndex.Title), column => column.Unlimited())
        );

        // 假设我们将在数据库中存储许多书籍，数万本。在这种情况下，还建议为经常查询的列添加SQL索引。
        // 在我们的例子中，Author就是这样的，所以我们在下面添加一个索引。注意，您只能在AlterTable()中添加索引。
        await SchemaBuilder.AlterTableAsync(nameof(BookIndex), table => table
            .CreateIndex($"IDX_{nameof(BookIndex)}_{nameof(BookIndex.Author)}", nameof(BookIndex.Author))
        );

        // 这里我们返回迁移的版本号。如果没有更新方法，我们会返回1。但我们有一个，请参阅它以获取更多详细信息。
        return 2;
    }

    /// <summary>
    /// 从版本1更新方法 - 更新现有架构
    /// 
    /// 功能说明：
    /// - 更新现有数据库架构
    /// - 添加缺失的列
    /// - 演示版本升级过程
    /// - 提供向后兼容性
    /// 
    /// 更新逻辑：
    /// - 检查当前版本是否为1
    /// - 添加Title列到现有表
    /// - 保持数据完整性
    /// - 返回新版本号
    /// 
    /// 版本管理：
    /// - 从版本1升级到版本2
    /// - 添加Title列支持
    /// - 演示架构变更过程
    /// </summary>
    /// <returns>完成的任务</returns>
    // 这是一个更新方法。它用于修改现有架构。当模块之前已经启用并且创建方法已经运行时，
    // 更新方法将运行（比如当您更新已经在Orchard站点上运行的模块时）。
    // UpdateFromX中的X是更新的版本号（方法名称是约定的）。这意味着：
    // "如果模块的当前迁移版本是X，则运行此更新"。如果版本是1，此方法将运行。
    public async Task<int> UpdateFrom1Async()
    {
        // 我们模块的初始版本没有存储书籍的标题。我们通过推送一个更新来快速修复这个问题，
        // 该更新修改架构以添加Name。记住，我们在Create方法中返回了2，所以这个更新方法
        // 在全新设置中不会执行。这就是为什么您还需要在Create方法中包含所有这些更改。
        await SchemaBuilder.AlterTableAsync(nameof(BookIndex), table => table
            .AddColumn<string>(nameof(BookIndex.Title))
        );

        return 2;
    }
}

// 下一站：Controllers/DatabaseStorageController，转到我们之前离开的CreateBooksPost操作。
